/*==============================================================================
 * AUTH: Matthew Baum, 9753095
 * 		 Domenico Romano, 5574331
 *       Samantha Graham, 1120689
 *		 Jon Leckman, 4960122
 *
 * DATE: January-27-2012
 * EDIT: April-08-2012 (Matt)
 *
 * DESC: Java class to hold Task data.
 *==============================================================================*/

package SOEN_Phase_3;

// W3C DOM Libraries
import org.w3c.dom.*;
import java.util.ArrayList;

public class Task
{

    // Task data
    private int ID, duration, timeInvested, assigneeID;
    private String title, assignee;
    private boolean complete;

    // Subtasks
    private ArrayList<Task> subtasks;

    // Dependencies
    private ArrayList<Task> dependencies = new ArrayList<Task>();
    private ArrayList<Integer> dependencyIDs;

    // Parent Task (null if top-level)
    private Task parent = null;


    /**
     * Constructor for a Task object
     *
     * @param  id A unique identifier
     * @param  dur Estimated time (in hours) to complete the task
     * @param  title A name for the Task
     * @param  assgn The name of the assignee this task is given to
     * @param  assgnID The unique identifier of the assignee this task is given to
     * @param  hours Number of hours worked on this task so far
     * @param  done Boolean flag indicating whether or not the task has been completed
     * @param  subs Array List of subtasks attached to the current task
     * @paaraam deps Array List of ints for task IDs for subtasks
     *
     * @return A newly constructed Task object
     *
     * @since January-27-2012
     */
    public Task(int id, int dur, String title, String assgn, int assgnID, int hours, boolean done, ArrayList<Task> subs, ArrayList deps)
    {
        ID = id;
        duration = dur;
        this.title = title;
        assignee = assgn;
        assigneeID = assgnID;
        timeInvested = hours;
        complete = done;
        subtasks = subs;
		dependencyIDs = deps;

        // Set current Task as parent on all subtasks
        setParentOnSubtasks();
    }


	public void clearSubTasks()
	{
		subtasks = new ArrayList<Task>();
	}

	public void clearDependencies()
	{
        dependencies.clear();
		dependencyIDs.clear();
	}

	public void checkDependencies()
	{
		ArrayList<Integer> temp = new ArrayList<Integer>();

		for(int i=0; i < dependencyIDs.size(); i++)
		{
			boolean exists = false;
			for(int j=0; j < subtasks.size(); j++)
			{
				if (dependencyIDs.get(i) == subtasks.get(j).getID() )
					exists = true;
			}
			if (exists) temp.add(dependencyIDs.get(i));
		}

		dependencyIDs = new ArrayList<Integer>(temp);
	}


    /**
     * Private helper to set "parent" attribute on all subtasks
     *
     * @return void
     */
    private void setParentOnSubtasks()
    {
        for(int i = 0; i != subtasks.size(); ++i)
        {
            subtasks.get(i).setParent(this);
        }
    }


    /**
     * Static method to create a Task from an XML DOM node
     *
     * DESC: A Task is created for a top-level node, and then
     * a Task is recursively created for each Subtask
     *
     * @param task An XML DOM node of "task" type, parsed from an XML data file
     * @return The Task object constructed from the XML DOM node
     */
    public static Task getTaskFromXMLNode(Node task)
    {
        // Variables to init a task object:
        int ID = 0, duration = 0, timeInvested = 0, assigneeID = 0;
        String title = "", assignee = "";
        boolean done = false;
        ArrayList<Task> subtasks = new ArrayList<Task>();
        ArrayList dependencies = new ArrayList();

        // Get the task attributes (ID AND duration)
        NamedNodeMap taskAttributes = task.getAttributes();

        // Get the child nodes (title AND assignee)
        NodeList taskContents = task.getChildNodes();

        // Get the task ID:
        ID = Integer.parseInt( taskAttributes.getNamedItem("ID").getNodeValue() );

        // Get the duration:
        duration = Integer.parseInt( taskAttributes.getNamedItem("duration").getNodeValue() );

        // Get the amount of time invested
        timeInvested = Integer.parseInt( taskAttributes.getNamedItem("worked").getNodeValue() );

        // Get the completion status of the Task
        done = Boolean.parseBoolean( taskAttributes.getNamedItem("complete").getNodeValue() );

        // Iterate over the child nodes
        for(int j = 0; j != taskContents.getLength(); ++j)
        {
            // Identify the node label
            String label = taskContents.item(j).getNodeName();

            // Set task title
            if( label.equals("title") )
            {
                title = taskContents.item(j).getTextContent();
            }

            // Set task assignee
            else if( label.equals("assignee") )
            {
                assignee = taskContents.item(j).getTextContent();
                assigneeID = Integer.parseInt( taskContents.item(j).getAttributes().getNamedItem("ID").getNodeValue() );
            }

            // Subtasks
            else if( label.equals("subtasks") )
            {
            	NodeList subs = taskContents.item(j).getChildNodes();
            	for(int m = 0; m < subs.getLength(); m++)
            	{
            		// Recursively create Task objects and add them to the subtask vector
            		if(subs.item(m).getNodeName().equals("subtask"))
            		{
            			Node sss = subs.item(m);
            			Task subt = Task.getTaskFromXMLNode(sss);
                		subtasks.add(subt);
            		}
            	}
            }

            // Dependencies
            else if( label.equals("dependencies") )
            {
            	NodeList deps = taskContents.item(j).getChildNodes();
                String depId;

            	for(int m = 0; m < deps.getLength(); ++m)
            	{
            		if(deps.item(m).getNodeName().equals("dep"))
            		{
                        depId = deps.item(m).getAttributes().item(0).getNodeValue();
                        dependencies.add(Integer.parseInt( depId ) );
                    }
            	}
            }


        }

        // Return Task object created from the values extracted from the XML DOM node
        return( new Task(ID, duration, title, assignee, assigneeID, timeInvested, done, subtasks, dependencies) );
    }


    /**
     * Create XML data from a Task object, and append it to an XML document
     *
     * @param doc XML Document object to create a Task element from
     * @param isSub Boolean flag identifying the Task as a subtask
     * @return The Task object constructed from the XML DOM node
     */
    public Element toXML(Document doc, boolean isSub)
    {
        // Create the task/subtask element
        Element taskElement;

        if(isSub)
        {
            taskElement = doc.createElement("subtask");
        }
        else
        {
            taskElement = doc.createElement("task");
        }

        // Add the ID attribute
        String tempID = Integer.toString(getID());
        taskElement.setAttribute("ID", tempID);

        // Add the duration attribute
        String tempDuration = Integer.toString(getDuration());
        taskElement.setAttribute("duration", tempDuration);

        // Add the worked attribute
        String tempWorked = Integer.toString(getTimeInvested());
        taskElement.setAttribute("worked", tempWorked);

        // Add the complete attribute
        String tempComplete = Boolean.toString(getCompletion());
        taskElement.setAttribute("complete", tempComplete);

        // Create the title element
        Element titleElement = doc.createElement("title");
        titleElement.appendChild(doc.createTextNode(getTitle()));
        taskElement.appendChild(titleElement);

        // Create the assignee element
        Element assigneeElement = doc.createElement("assignee");
        assigneeElement.setAttribute("ID", Integer.toString( getAssigneeID() ) );
        assigneeElement.appendChild(doc.createTextNode(getAssignee()));
        taskElement.appendChild(assigneeElement);

        // Create subtasks element
        Element subtasksElement = doc.createElement("subtasks");

        // Recursively append a child element, for each subtask
        for(int i = 0; i != subtasks.size(); ++i)
        {
            subtasksElement.appendChild( subtasks.get(i).toXML(doc, true) );
        }

        // Append the subtasks element to the Task XML
        taskElement.appendChild(subtasksElement);

        Element dependencyElement = doc.createElement("dependencies");

        //add each element
        for(int i = 0; i != dependencyIDs.size(); i++){
        	String tempDep = dependencyIDs.get(i).toString();
        	Element x = doc.createElement("dep");
        	x.setAttribute("dep", tempDep);
        	dependencyElement.appendChild(x);
        }
        taskElement.appendChild(dependencyElement);

        return taskElement;
    }


    /**
     * Function used to remove a subtask from a task object's, subtask list
     *
     * @param ID The unique identifier of the Task to be removed
     * @return void
     */
    public void removeSubtask(int ID)
    {
        Task subtask;

        for(int i = 0; i != subtasks.size(); ++i)
        {
            subtask = subtasks.get(i);

            // If the correct task is found, remove and return
            if(subtask.ID == ID)
            {
                // Remove subtask
                subtasks.remove(i);

                // Remove parent ref from subtask
                subtask.parent = null;

                // Remove dependency from parent (this)
                this.removeDependency(subtask.ID);

                return;
            }
        }
    }


    /**
     * Remove the current Task object from its parent's list of subtasks
     *
     * This method delegates its behavior to "removeSubtask(int ID)"
     *
     * @return The Task object constructed from the XML DOM node
     */
    public void removeFromParent()
    {
        if(isSubtask())
        {
            parent.removeSubtask(ID);
        }
    }


    /**
     * Print a String representation of a Task
     *
     * @return A String representation of a Task
     */
    @Override
    public String toString()
    {
        return("TASK #" + ID + ": " + title + ".\tDuration: " + duration +
                ".\tAssigned to: " + assignee + ", " + assigneeID +
                "\nHOURS INVESTED: " + timeInvested + " COMPLETED: " + complete);
    }


    /**
     * Compute a hash code from a Task
     *
     * @return An integer hash code
     */
    @Override
    public int hashCode()
    {
        // Return the Task ID, which is assumed to be unique
        return ID;
    }


    /**
     * Compares two Tasks for equality, used in hash table
     *
     * @param other Object representing another item to compare the task to
     * @return Boolean equality result
     */
    @Override
    public boolean equals(Object other)
    {
        // Tasks are considered equal if they have the same ID
        if(other instanceof Task)
        {
            Task otherTask = (Task)other;

            if(this.ID == otherTask.ID)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        // They are not considered equal!
        return false;
    }

    // GETTERS AND SETTERS
    public int getID() {
		return ID;
	}

	public int getDuration() {
		return duration;
	}

	public String getTitle() {
		return title;
	}

	public String getAssignee() {
		return assignee;
	}

    public int getAssigneeID()
    {
        return assigneeID;
    }

    public int getTimeInvested()
    {
        return timeInvested;
    }

    public boolean getCompletion()
    {
        return complete;
    }

    public ArrayList<Task> getSubtasks()
    {
        return subtasks;
    }

    /*---------------------------------------------
     * Special getter to return all subtasks, and
     * THEIR subtasks, recursively
     *---------------------------------------------*/
    public ArrayList<Task> getSubtasksDeep()
    {
        ArrayList<Task> deepSubtasks = subtasks;
        Task subtask;

        // Iterate over subtasks
        for(int i = 0; i != deepSubtasks.size(); ++i)
        {
            // Get subtask
            subtask = deepSubtasks.get(i);

            // IF that subtask has subtasks, recurse
            if(subtask.getSubtasks().size() > 0)
            {
                deepSubtasks.addAll( subtask.getSubtasksDeep() );
            }
        }

        // Return total list of subtasks
        return deepSubtasks;
    }

    /*---------------------------------------------
     * Special getter to return all dependencies, and
     * THEIR dependencies, recursively
     *---------------------------------------------*/
    public ArrayList<Task> getDependenciesDeep()
    {
        ArrayList<Task> deepDeps = dependencies;
        Task dependency;

        // Iterate over deps
        for(int i = 0; i != deepDeps.size(); ++i)
        {
            // Get dep
            dependency = deepDeps.get(i);

            // IF that dep has deps, recurse
            if(dependency.getDependencies().size() > 0)
            {
                deepDeps.addAll( dependency.getDependenciesDeep() );
            }
        }

        // Return total list of dependencies
        return deepDeps;
    }

	public ArrayList<Integer> getDependencyIDs()
	{
		return dependencyIDs;
	}

	public ArrayList<Task> getDependencies()
	{
		return dependencies;
	}

	/**
	 * Method: addDependency
	 *
	 * Tries to add a dependency to the list, returns false if dependency
     * already existed.
	 *
	 * @param dep The Task to add as a dependency
	 * @return boolean true if new dependency added, false otherwise
	 */
	public boolean addDependency(Task dep)
	{
        ArrayList<Task> deepDeps = dep.getDependenciesDeep();

        /*------------------------------
         * PREVENT:
         * - Redundant dependencies
         * - Circular dependencies
         * - Self-dependencies
         *------------------------------*/
        if
        (
            ! dependencies.contains(dep) &&
            ! deepDeps.contains(this) &&
            dep.ID != ID
        )
        {
            // Add dependency
            dependencies.add(dep);
            return true;
        }

        // ELSE: dependency was NOT added
        else
        {
            return false;
        }
	}

	/**
	 * Method: addDependency
	 *
	 * Tries to add a dependency to the list, returns false if dependency
     * already existed.
	 *
	 * @param int Dependency ID to add
	 * @return boolean true if new dependency, false if not
	 */
	public boolean addDependencyID(int dep)
	{
        /*------------------------------
         * PREVENT:
         * - Redundant dependencies
         *------------------------------*/
        if
        (
            ! dependencyIDs.contains(dep)
        )
        {
            // Add dependency
            dependencyIDs.add(dep);
            return true;
        }
        
        // ELSE: dependency was NOT added
        else
        {
            return false;
        }
	}

	/**
	 *removeDependency
	 *
	 *attempts to remove a dependency, returns true if successful, false if not
	 *
	 *@param int Dependency to remove
	 *@return boolean True if successful, false if not
	 */
	public boolean removeDependency(int dep)
	{
		for(int i = 0; i < dependencies.size(); i++)
		{
			if (dep == dependencies.get(i).ID)
			{
                // TRY to remove the dependency (if it exists)
				dependencyIDs.remove((Integer)dependencies.get(i).ID);
                dependencies.remove(i);

                // TRY to remove the corresponding subtask (if it exists)
                this.removeSubtask(dep);

				return true;
			}
		}
		return false;
	}


    public void setID(int id) {
		ID = id;
	}

	public void setDuration(int dur) {
		duration = dur;
	}

	public void setTitle(String ttl) {
		title = ttl;
	}

	public void setAssignee(String assgn) {
		assignee = assgn;
	}

    public void setAssigneeID(int id)
    {
        assigneeID = id;
    }

    public void setTimeInvested(int time)
    {
        timeInvested = time;
    }

    public Task getParent()
    {
    	return parent;
    }

    public void addSubtask(Task st)
    {
        // Get deep subtasks of the potential subtask
        ArrayList<Task> deepSubtasks = st.getSubtasksDeep();

        /*---------------------------------------------------------
         * Verify that:
         * - A Task is not subtasking itself
         * - A Task is not subtasking a subtask AGAIN
         * - A task is not subtasking a Subtask from another task
         * - This function is not creating a circular relationship
         *---------------------------------------------------------*/
        if
        (
            ! equals(st) &&
            ! st.isSubtask() &&
            ! deepSubtasks.contains(this)
        )
        {
            // Add subtask
            subtasks.add(st);
            setParentOnSubtasks();

            // Generate a dependency
            addDependency(st);
        }
    }

    public void addTimeInvested(int time)
    {
        timeInvested += time;
        if (timeInvested > duration)
        {
        	timeInvested = duration;
        }
    }

	/**
	 *setCompletion
	 *
	 *Attempts to complete the job, checks all dependencies for completion before allowing, returns boolean on success
	 *
	 *@param boolean completion to set
	 *@return boolean returns status of completion
	 */

    public boolean setCompletion(boolean done)
    {
    	//If user is attempting to incomplete a task always allow
    	if (!done)
    	{
    		complete = false;
    		return false;
    	}

    	//Check for dependency completion before allowing completion
    	boolean subDone = true;

        for (int j = 0; j < dependencyIDs.size(); j++)
        {
        	for (int k = 0; k < subtasks.size(); k++)
        	{
        		if ( dependencyIDs.get(j) == subtasks.get(k).getID() )
        		{
        			subDone = ( subDone && (subtasks.get(k).getCompletion()) );
        		}
        	}
        }

        if (subDone)
        {
        	complete = true;
        	return true;
        }
        else
        {
        	complete = false;
        	return false;
        }

    }

    public void setParent(Task t)
    {
    	parent = t;
    }


    // Return subtask status flag
    public boolean isSubtask()
    {
        return (parent != null);
    }
}
